' 
' Copyright (C) 2014 Infinity CCS Ltd (http://www.infinityccs.com/)
' Copyright (C) 2007 Sasa Coh <sasacoh@gmail.com>
'
' This program is free software; you can redistribute it and/or modify
' it under the terms of the GNU General Public License as published by
' the Free Software Foundation; either version 3 of the License, or
' (at your option) any later version.
'
' This program is distributed in the hope that it will be useful,
' but WITHOUT ANY WARRANTY; without even the implied warranty of
' MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
' GNU General Public License for more details.
'
' You should have received a copy of the GNU General Public License
' along with this program.  If not, see <http://www.gnu.org/licenses/>. 
'/


Imports System
Imports Infinity.pjsipNET.Common
Imports Infinity.pjsipNET.CallControl.CallState
Imports Infinity.pjsipNET.Common.CallState
Imports Infinity.pjsipNET.Common.Media
Imports Infinity.pjsipNET.Common.Config

Namespace CallControl

  ''' <summary>
  ''' CStateMachine class is a telephony data container for signle call. It maintains call state, 
  ''' communicates with signaling via proxy and informs about events from signaling.
  ''' A Finite State Machine is implemented in State design pattern!
  ''' </summary>
  Public Class CallMachine
    Inherits IStateMachine
#Region "Variables"

    Private _state As IState
    '// State instances....
    Private _stateIdle As Idle
    Private _stateCalling As Connecting
    Private _stateAlerting As Alerting
    Private _stateActive As Active
    Private _stateReleased As Released
    Private _stateIncoming As Incoming
    Private _stateHolding As Holding
    Private _stateTerminated As Terminated
    '// call properties
    Private _callType As CallLog.ECallType
    Private _duration As TimeSpan
    Private _timestamp As DateTime
    Private _manager As CallControl.CallManager
    '// Timers
    Protected _noanswerTimer As Timer.ITimer
    Protected _releasedTimer As Timer.ITimer
    Protected _noresponseTimer As Timer.ITimer

    Private _sessionId As Integer = -1
    Private _accountId As Integer = -1
    Private _line As Integer = 0
    Private _sigProxy As ICallProxy
    Private _callingNumber As String = ""
    Private _callingName As String = ""
    Private _ddi As String = ""
    Private _incoming As Boolean = False
    Private _isHeld As Boolean = False
    Private _is3Pty As Boolean = False
    Private _counting As Boolean = False '// if duration counter is started
    Private _holdRequested As Boolean = False
    Private _retrieveRequested As Boolean = False
    Private _disableStateNotifications As Boolean = False

    Private _lastStatus As Integer = 0
    Private _lastStatusString As String = ""

#End Region 'Variables

#Region "Properties"
    ''' <summary>
    ''' A reference to CCallManager instance
    ''' </summary>
    Public ReadOnly Property Manager As CallManager
      Get
        Return _manager
      End Get
    End Property

    ''' <summary>
    ''' Call/Session identification
    ''' </summary>
    Public Overrides Property SessionId As Integer
      Get
        Return _sessionId
      End Get
      Set(value As Integer)
        _sessionId = value
        Me.CallProxy.SessionId = value
      End Set
    End Property

    Public Overrides Property AccountId As Integer
      Get
        Return _accountId
      End Get
      Set(value As Integer)
        _accountId = value
        Me.CallProxy.AccountId = value
      End Set
    End Property

    ' ''' <summary>
    ' ''' Call/Session identification
    ' ''' </summary>
    'Public Overrides Property AccountKey As String
    '  Get
    '    Return _accountKey
    '  End Get
    '  Set(value As String)
    '    _accountKey = value
    '  End Set
    'End Property

    ''' <summary>
    ''' Line identification
    ''' </summary>
    Public Overrides Property Line As Integer
      Get
        Return _line
      End Get
      Set(value As Integer)
        _line = value
      End Set
    End Property

    ''' <summary>
    ''' Calling number property
    ''' </summary>
    Public Overrides Property CallingNumber As String
      Get
        Return _callingNumber
      End Get
      Set(value As String)
        _callingNumber = value
      End Set
    End Property


    ''' <summary>
    ''' Calling name property
    ''' </summary>
    Public Overrides Property CallingName As String
      Get
        Return _callingName
      End Get
      Set(value As String)
        _callingName = value
      End Set
    End Property

    ''' <summary>
    ''' DDI property
    ''' </summary>
    Public Overrides Property DDI As String
      Get
        Return _ddi
      End Get
      Set(value As String)
        _ddi = value
      End Set
    End Property

    ''' <summary>
    ''' Incoming call flag
    ''' </summary>
    Public Overrides Property Incoming As Boolean
      Get
        Return _incoming
      End Get
      Set(value As Boolean)
        _incoming = value
      End Set
    End Property

    ''' <summary>
    ''' Is call held by other side
    ''' </summary>
    Public Overrides Property IsHeld As Boolean
      Get
        Return _isHeld
      End Get
      Set(value As Boolean)
        _isHeld = value
      End Set
    End Property

    ''' <summary>
    ''' Is this call 3pty (==2 active sessions)
    ''' </summary>
    Public Overrides Property Is3Pty As Boolean
      Get
        Return _is3Pty
      End Get
      Set(value As Boolean)
        _is3Pty = value
      End Set
    End Property

    ''' <summary>
    ''' 
    ''' </summary>
    Public Overrides ReadOnly Property StateId As EStateId
      Get
        Return _state.Id
      End Get
    End Property

    ''' <summary>
    ''' ???
    ''' </summary>
    Friend Overrides Property Counting As Boolean
      Get
        Return _counting
      End Get
      Set(value As Boolean)
        _counting = value
      End Set
    End Property

    ''' <summary>
    ''' Duration of a call
    ''' </summary>
    Public Overrides Property Duration As TimeSpan
      Get
        Return _duration
      End Get
      Set(value As TimeSpan)
        _duration = value
      End Set
    End Property

    ''' <summary>
    ''' Calculate call duration
    ''' </summary>
    Public Overrides ReadOnly Property RuntimeDuration As System.TimeSpan
      Get
        If Counting Then
          Return DateTime.Now.Subtract(Time)
        Else
          Return Duration
        End If
      End Get
    End Property

    ''' <summary>
    ''' Current State of the state machine
    ''' </summary>
    Friend Overrides ReadOnly Property State As IState
      Get
        Return _state
      End Get
    End Property

    ''' <summary>
    ''' Check for null state machine
    ''' </summary>
    Public Overrides ReadOnly Property IsNull As Boolean
      Get
        Return False
      End Get
    End Property

    ''' <summary>
    ''' Signaling proxy instance (separatelly created for each call)
    ''' </summary>
    Friend Overrides ReadOnly Property CallProxy As ICallProxy
      Get
        Return _sigProxy
      End Get
    End Property

    ''' <summary>
    ''' Media proxy instance getter for handling tones
    ''' </summary>
    Friend Overrides ReadOnly Property MediaProxy As IMediaProxy
      Get
        Return _manager.MediaProxy
      End Get
    End Property

    ''' <summary>
    ''' Call type property for Call log
    ''' </summary>
    Friend Overrides Property Type As CallLog.ECallType
      Get
        Return _callType
      End Get
      Set(value As CallLog.ECallType)
        _callType = value
      End Set
    End Property

    ''' <summary>
    ''' Timestamp of a call
    ''' </summary>
    Friend Overrides Property Time As DateTime
      Get
        Return _timestamp
      End Get
      Set(value As DateTime)
        _timestamp = value
      End Set
    End Property

    ''' <summary>
    ''' Has been call hold requested
    ''' </summary>
    Friend Overrides Property HoldRequested As Boolean
      Get
        Return _holdRequested
      End Get
      Set(value As Boolean)
        _holdRequested = value
      End Set
    End Property

    ''' <summary>
    ''' Has been call retrieve requested
    ''' </summary>
    Friend Overrides Property RetrieveRequested As Boolean
      Get
        Return _retrieveRequested
      End Get
      Set(value As Boolean)
        _retrieveRequested = value
      End Set
    End Property


    ''' <summary>
    ''' Data access instance
    ''' </summary>
    Friend Overrides ReadOnly Property Config As IConfigurator
      Get
        Return _manager.Config
      End Get
    End Property

    ''' <summary>
    ''' Call log instance
    ''' </summary>
    Protected ReadOnly Property CallLoger As CallLog.ICallLog
      Get
        Return _manager.CallLogger
      End Get
    End Property

    ''' <summary>
    ''' Get Current codec for this call
    ''' </summary>
    ''' <returns>Codec name</returns>
    Public Overrides ReadOnly Property Codec As String
      Get
        Return _sigProxy.getCurrentCodec
      End Get
    End Property

    Friend Overrides Property DisableStateNotifications As Boolean
      Get
        Return _disableStateNotifications
      End Get
      Set(value As Boolean)
        _disableStateNotifications = value
      End Set
    End Property

    Friend Overrides ReadOnly Property NumberOfCalls As Integer
      Get
        Return Manager.Count
      End Get
    End Property

    ''' <summary>
    ''' Last pjsip Status
    ''' </summary>
    Public Overrides Property LastStatus As Integer
      Get
        Return _lastStatus
      End Get
      Set(value As Integer)
        _lastStatus = value
      End Set
    End Property

    ''' <summary>
    ''' Last pjsip Status
    ''' </summary>
    Public Overrides Property LastStatusText As String
      Get
        Return _lastStatusString
      End Get
      Set(value As String)
        _lastStatusString = value
      End Set
    End Property

#End Region

#Region "Constructor"

    ''' <summary>
    ''' Call/Session constructor. Initializes call states, creates signaling proxy, initialize time,
    ''' initialize timers.
    ''' </summary>
    Public Sub New()
      '// store manager reference...
      _manager = CallManager.Instance

      '// create call proxy
      _sigProxy = _manager.StackProxy.createCallProxy()

      '// initialize call states
      _stateIdle = New Idle(Me)
      _stateAlerting = New Alerting(Me)
      _stateActive = New Active(Me)
      _stateCalling = New Connecting(Me)
      _stateReleased = New Released(Me)
      _stateIncoming = New Incoming(Me)
      _stateHolding = New Holding(Me)
      _stateTerminated = New Terminated(Me)
      '// change state
      _state = _stateIdle

      '// initialize data
      Time = DateTime.Now
      Duration = TimeSpan.Zero

      '// Initialize timers
      If _manager IsNot Nothing Then
        _noanswerTimer = _manager.Factory.createTimer()
        _noanswerTimer.Interval = 15000 '// hardcoded to 15s
        _noanswerTimer.Elapsed = New Timer.TimerExpiredCallback(AddressOf _noanswerTimer_Elapsed)

        _releasedTimer = _manager.Factory.createTimer()
        _releasedTimer.Interval = 5000 '// hardcoded to 5s
        _releasedTimer.Elapsed = New Timer.TimerExpiredCallback(AddressOf _releasedTimer_Elapsed)

        _noresponseTimer = _manager.Factory.createTimer()
        _noresponseTimer.Interval = 60000 '// hardcoded to 60s
        _noresponseTimer.Elapsed = New Timer.TimerExpiredCallback(AddressOf _noresponseTimer_Elapsed)

      End If
    End Sub

#End Region 'Constructor

#Region "Private Methods"
    ''' <summary>
    ''' Change state
    ''' </summary>
    ''' <param name="state">instance of state to change to</param>
    Private Overloads Sub ChangeState(state As IState)
      _state.OnExit()
      _state = state
      _state.OnEntry()
    End Sub

    ''' <summary>
    ''' 
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    Private Sub _noanswerTimer_Elapsed(sender As Object, e As EventArgs)
      State.noAnswerTimerExpired(Me.SessionId)
    End Sub

    ''' <summary>
    ''' 
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    Private Sub _releasedTimer_Elapsed(sender As Object, e As EventArgs)
      State.releasedTimerExpired(Me.SessionId)
    End Sub

    ''' <summary>
    ''' 
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    Private Sub _noresponseTimer_Elapsed(sender As Object, e As EventArgs)
      State.noResponseTimerExpired(Me.SessionId)
    End Sub

    '///////////////////////////////////////////////////////////////////////////////////
    '// Timers
    ''' <summary>
    ''' Start timer by timer type
    ''' </summary>
    ''' <param name="ttype">timer type</param>
    Friend Overrides Function StartTimer(ttype As Timer.ETimerType, numDelay As Integer) As Boolean
      Dim success As Boolean = False
      Select Case ttype
        Case Timer.ETimerType.ENOANSWER
          success = _noanswerTimer.Start()
        Case Timer.ETimerType.ERELEASED
          success = _releasedTimer.Start()
        Case Timer.ETimerType.ENORESPONSE
          success = _noresponseTimer.Start()
      End Select
      Return success
    End Function

    ''' <summary>
    ''' Stop timer by timer type
    ''' </summary>
    ''' <param name="ttype">timer type</param>
    Friend Overrides Function StopTimer(ttype As Timer.ETimerType) As Boolean
      Dim success As Boolean = False
      Select Case ttype
        Case Timer.ETimerType.ENOANSWER
          success = _noanswerTimer.Stop()
        Case Timer.ETimerType.ERELEASED
          success = _releasedTimer.Stop()
        Case Timer.ETimerType.ENORESPONSE
          success = _noresponseTimer.Stop()
      End Select
      Return success
    End Function

    ''' <summary>
    ''' Stop all timers...
    ''' </summary>
    Friend Overrides Sub StopAllTimers()
      _releasedTimer.Stop()
      _noresponseTimer.Stop()
    End Sub

    ''' <summary>
    ''' Run queued requests
    ''' </summary>
    Friend Overrides Sub ActivatePendingAction()
      Manager.activatePendingAction()
    End Sub

    ''' <summary>
    ''' Change state by state id
    ''' </summary>
    ''' <param name="stateId">state id</param>
    Friend Overrides Sub ChangeState(stateId As EStateId)
      Select Case stateId
        Case EStateId.IDLE : ChangeState(_stateIdle)
        Case EStateId.CONNECTING : ChangeState(_stateCalling)
        Case EStateId.ALERTING : ChangeState(_stateAlerting)
        Case EStateId.ACTIVE : ChangeState(_stateActive)
        Case EStateId.RELEASED : ChangeState(_stateReleased)
        Case EStateId.INCOMING : ChangeState(_stateIncoming)
        Case EStateId.HOLDING : ChangeState(_stateHolding)
        Case EStateId.TERMINATED : ChangeState(_stateTerminated)
      End Select

      '// inform manager 
      If ((_manager IsNot Nothing) AndAlso (SessionId <> -1) AndAlso (DisableStateNotifications = False)) Then _manager.updateGui(Me.SessionId)
    End Sub
#End Region

#Region "Public Methods"

    ''' <summary>
    ''' Destroy call. Calculate call duration time, edit call log, destroy session.
    ''' </summary>
    Public Overrides Sub Destroy()
      '// stop tones & timers
      StopAllTimers()

      MediaProxy.stopTone()

      '// Calculate timing
      If (True = Counting) Then
        Duration = DateTime.Now.Subtract(Time)
      End If
      Counting = False

      '// update call log
      If (((Type <> CallLog.ECallType.EDialed) OrElse (CallingNumber.Length > 0)) AndAlso (Type <> CallLog.ECallType.EUndefined)) Then
        CallLoger.addCall(Type, CallingNumber, CallingName, Time, Duration)
        CallLoger.save()
      End If
      '// reset data
      CallingNumber = ""
      Incoming = False
      ChangeState(EStateId.IDLE)
      If (Nothing IsNot _manager) Then _manager.DestroySession(SessionId)
    End Sub

#End Region 'Methods

  End Class

End Namespace '// namespace Common.CallControl
